#include "linkedlist.h"
#include <stdlib.h>

/**
 * Returns the data on the front of the list.
 */
void *ll_get_front(ll_list_t *list)
{
	return list->head->data;
}

/**
 * Returns the data on the back of the list.
 */
void *ll_get_back(ll_list_t *list)
{
	return list->tail->data;
}

/**
 * Adds data to the front of the list and returns the new node.
 * Returns NULL on error.
 */
ll_node_t *ll_add_front(ll_list_t *list, void *data)
{
	ll_node_t *node = malloc(sizeof(ll_node_t));

	if (node != NULL)
	{
		node->data = data;
		node->next = list->head;

		if (list->head == NULL)
		{
			list->tail = node;
		}

		list->head = node;
		++list->count;
	}

	return node;
}

/**
 * Adds data to the back of the list and returns the new node.
 * When maxCount > 0 and adding the new node would cause the list to exceed
 * maxCount, the first item in the list is removed and the data is placed in truncatedData.
 * Otherwise, truncatedData will contain NULL.
 * Returns NULL on error.
 */
ll_node_t *ll_add_back(ll_list_t *list, void *data, unsigned int maxCount, void **truncatedData)
{
	ll_node_t *node;

	if (maxCount > 0 && list->count >= maxCount)
	{
		node = list->head;
		list->head = node->next;
		--list->count;
		*truncatedData = node->data;
	}
	else
	{
		node = malloc(sizeof(ll_node_t));
		*truncatedData = NULL;
	}

	if (node != NULL)
	{
		node->data = data;
		node->next = NULL;

		if (list->tail != NULL)
		{
			list->tail->next = node;
		}
		else
		{
			list->head = node;
		}

		list->tail = node;
		++list->count;
	}

	return node;
}

/**
 * Removes the first node from the list and returns its data.
 */
void *ll_remove_front(ll_list_t *list)
{
	ll_node_t *node = list->head;

	if (node == NULL)
	{
		return NULL;
	}

	void *data = node->data;

	list->head = node->next;
	--list->count;

	if (list->head == NULL)
	{
		list->tail = NULL;
	}

	free(node);
	return data;
}

/**
 * Removes the last node from the list and returns its data.
 */
void *ll_remove_back(ll_list_t *list)
{
	return ll_remove(list, list->tail);
}

/**
 * Inserts data into a list after prevNode and returns the new node.
 */
ll_node_t *ll_insert(ll_list_t *list, ll_node_t *prevNode, void *data)
{
	if (prevNode == NULL)
	{
		return ll_add_front(list, data);
	}

	ll_node_t *node = malloc(sizeof(ll_node_t));

	if (node != NULL)
	{
		node->data = data;
		node->next = prevNode->next;

		if (prevNode->next == NULL)
		{
			list->tail = node;
		}

		prevNode->next = node;
		++list->count;
	}

	return node;
}

/**
 * Removes a node from the list and returns its data.
 */
void *ll_remove(ll_list_t *list, ll_node_t *node)
{
	if (list->head == node)
	{
		return ll_remove_front(list);
	}

	ll_node_t *prev = list->head;

	while (prev->next != node)
	{
		if (prev->next == NULL)
		{
			return NULL;
		}

		prev = prev->next;
	}

	prev->next = node->next;

	if (prev->next == NULL)
	{
		list->tail = prev;
	}

	--list->count;

	void *data = node->data;
	free(node);
	return data;
}

/**
 * Removes all nodes from the list and optionally frees the data.
 */
void ll_clear(ll_list_t *list, int freeContents)
{
	while (list->head != NULL)
	{
		void *data = ll_remove_front(list);

		if (freeContents)
		{
			free(data);
		}
	}
}

